package net.oxygen.upload.service.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.file.FileNameUtil;
import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.util.IdUtil;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Multimap;
import io.minio.ListPartsResponse;
import io.minio.UploadPartResponse;
import io.minio.messages.Part;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import net.oxygen.upload.common.utils.CommonUtil;
import net.oxygen.upload.enums.BizCodeEnum;
import net.oxygen.upload.model.Chunk;
import net.oxygen.upload.model.MultipartUploadCreate;
import net.oxygen.upload.model.UploadPartCreate;
import net.oxygen.upload.model.bo.CreateUploadUrlReqBO;
import net.oxygen.upload.model.bo.CreateUploadUrlRespBO;
import net.oxygen.upload.model.dto.FileMetadataInfoDTO;
import net.oxygen.upload.model.dto.FileMetadataInfoSaveDTO;
import net.oxygen.upload.model.dto.FileMetadataInfoUpdateDTO;
import net.oxygen.upload.model.enums.MinioPlusErrorCode;
import net.oxygen.upload.model.enums.StorageBucketEnums;
import net.oxygen.upload.model.vo.CompleteResultVo;
import net.oxygen.upload.model.vo.FileCheckResultVo;
import net.oxygen.upload.model.vo.FileChunkVo;
import net.oxygen.upload.model.vo.FileMetadataInfoVo;
import net.oxygen.upload.repository.MetadataRepository;
import net.oxygen.upload.request.CompleteMultipartUploadRequest;
import net.oxygen.upload.request.MultipartUploadCreateRequest;
import net.oxygen.upload.request.UploadPartRequest;
import net.oxygen.upload.response.FileUploadResponse;
import net.oxygen.upload.response.MultipartUploadCreateResponse;
import net.oxygen.upload.service.IFileService;
import net.oxygen.upload.service.IMinioService;
import net.oxygen.upload.util.JsonData;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import java.io.IOException;
import java.io.InputStream;
import java.util.*;

/**
 * <p>
 * 功能：文件操作服务类
 * </p>
 *
 * @author 陈伟伟
 * @since 2023/12/18 16:34
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class FileServiceImpl implements IFileService {

    private final IMinioService minioService;

    @Value("${minio.bucketName}")
    private String bucketName;

    @Value("${minio.endpoint}")
    private String endpoint;

    @Resource
    MetadataRepository metadataRepository;

    @Override
    public JsonData uploadFile(MultipartFile file) {
        if (file == null) {
            return JsonData.result(BizCodeEnum.BAD_REQUEST, "文件不能为空");
        }
        String filePath = minioService.uploadFile(file, bucketName);
        return JsonData.success(getFileUrl(filePath));
    }

    @Override
    public MultipartUploadCreateResponse createMultipartUpload(MultipartUploadCreateRequest request) {
        String extName = FileNameUtil.extName(request.getFileName());
        String fileName = IdUtil.fastSimpleUUID() + "." + extName;
        String path = DateUtil.format(new DateTime(), "yyyy/MM/dd");
        String objectName = path + "/" + fileName;
        Multimap<String, String> headers = HashMultimap.create();
        headers.put("Content-Type", request.getContentType());
        MultipartUploadCreate uploadCreate = MultipartUploadCreate.builder()
                .bucketName(bucketName)
                .objectName(objectName)
                .headers(headers)
                .build();
        String uploadId = minioService.uploadId(uploadCreate).result().uploadId();
        log.info("文件上传id：{}，文件名：{}", uploadId, objectName);
        return MultipartUploadCreateResponse.builder()
                .uploadId(uploadId)
                .fileName(objectName)
                .chunks(request.getChunks())
                .build();
    }


    @Override
    public String getUploadId(Chunk chunk) {
        String extName = FileNameUtil.extName(chunk.getFilename());
        String fileName = IdUtil.fastSimpleUUID() + "." + extName;
        String path = DateUtil.format(new DateTime(), "yyyy/MM/dd");
        String objectName =chunk.getFilename(); //path + "/" + fileName;
        Multimap<String, String> headers = HashMultimap.create();
        headers.put("Content-Type", chunk.getType());
        MultipartUploadCreate uploadCreate = MultipartUploadCreate.builder()
                .bucketName(bucketName)
                .objectName(objectName)
                .headers(headers)
                .build();
        String uploadId = minioService.uploadId(uploadCreate).result().uploadId();
        log.info("文件上传id：{}，文件名：{}", uploadId, objectName);
        return  uploadId;

//        return MultipartUploadCreateResponse.builder()
//                .uploadId(uploadId)
//                .fileName(objectName)
//                .chunks(request.getChunks())
//                .build();
    }

    @Override
    public UploadPartResponse uploadPart(UploadPartRequest request) {
        try (InputStream in = request.getFile().getInputStream()) {
            UploadPartCreate create =  UploadPartCreate.builder()
                    .bucketName(bucketName)
                    .objectName(request.getFileName())
                    .uploadId(request.getUploadId())
                    .partNumber(request.getPartNumber())
                    .data(in)
                    .length(request.getFile().getSize())
                    .build();
            return minioService.uploadPart(create);
        } catch (IOException e) {
            log.error("分片上传文件流处理失败", e);
            return null;
        }
    }

    @Override
    public UploadPartResponse uploadPart(Chunk chunk,String upid) {
        try (InputStream in = chunk.getFile().getInputStream()) {
            UploadPartCreate create =  UploadPartCreate.builder()
                    .bucketName(bucketName)
                    .objectName(chunk.getFilename())
                    .uploadId(upid)
                    .partNumber(chunk.getChunkNumber())
                    .data(in)
                    .length(chunk.getCurrentChunkSize())
                    .build();
            return minioService.uploadPart(create);
        } catch (IOException e) {
            log.error("分片上传文件流处理失败", e);
            return null;
        }
    }

    @Override
    public Part[] listParts(CompleteMultipartUploadRequest request) {
        ListPartsResponse listMultipart = minioService.listMultipartUploads(MultipartUploadCreate.builder()
                .bucketName(bucketName)
                .objectName(request.getFileName())
                .maxParts(request.getChunks() + 10)
                .uploadId(request.getUploadId())
                .partNumberMarker(0)
                .build());
        return listMultipart.result().partList().toArray(new Part[]{});
    }

    @Override
    public FileUploadResponse completeMultipartUpload(CompleteMultipartUploadRequest uploadRequest) {
        Part[] parts = listParts(uploadRequest);
        minioService.completeMultipartUpload(MultipartUploadCreate.builder()
                .bucketName(bucketName)
                .uploadId(uploadRequest.getUploadId())
                .objectName(uploadRequest.getFileName())
                .parts(parts)
                .build());
        return FileUploadResponse.builder()
                .url(getFileUrl(uploadRequest.getFileName()))
                .build();
    }

    public FileUploadResponse completeMultipartUpload(Chunk chunk) {
        CompleteMultipartUploadRequest uploadRequest=new  CompleteMultipartUploadRequest();
        uploadRequest.setChunks(chunk.getTotalChunks());
        uploadRequest.setContentType(chunk.getType());
        uploadRequest.setFileName(chunk.getFilename());
        uploadRequest.setFileSize(chunk.getTotalSize());
        uploadRequest.setUploadId(chunk.getUpid());
        FileUploadResponse fileUploadResponse=new FileUploadResponse();
        try{
        Part[] parts = listParts(uploadRequest);
        minioService.completeMultipartUpload(MultipartUploadCreate.builder()
                .bucketName(bucketName)
                .uploadId(uploadRequest.getUploadId())
                .objectName(uploadRequest.getFileName())
                .parts(parts)
                .build());
            fileUploadResponse= FileUploadResponse.builder()
                .url(getFileUrl(uploadRequest.getFileName()))
                .build();
            fileUploadResponse.setIsComplete(true);
            return fileUploadResponse;
        }
        catch (Exception e){
            fileUploadResponse.setIsComplete(false);
            return  fileUploadResponse;
        }

    }


    public CompleteResultVo complete(Chunk chunk,String userId){
        CompleteResultVo completeResultVo = new CompleteResultVo();

        FileMetadataInfoDTO searchDto = new FileMetadataInfoDTO();
        // 用户id
        searchDto.setCreateUser(userId);
        // 文件key

            searchDto.setFileKey(chunk.getIdentifier());


        FileMetadataInfoVo metadata = metadataRepository.one(searchDto);
        if(metadata == null){
            log.error(MinioPlusErrorCode.FILE_EXIST_FAILED.getMessage());
            //throw new MinioPlusException(MinioPlusErrorCode.FILE_EXIST_FAILED.getCode(),fileKey+MinioPlusErrorCode.FILE_EXIST_FAILED.getMessage());
        }
        if(Boolean.TRUE.equals(metadata.getIsFinished())){
            // 如果文件已上传完成，直接返回true，不进行合并
            completeResultVo.setIsComplete(true);
            return completeResultVo;
        }
        FileUploadResponse fileUploadResponse=completeMultipartUpload(chunk);
        if (Boolean.TRUE.equals(fileUploadResponse.getIsComplete())) {

            // 更新自己上传的文件元数据状态
            FileMetadataInfoUpdateDTO updateDTO = new FileMetadataInfoUpdateDTO();
            updateDTO.setId(metadata.getId());
            updateDTO.setIsFinished(Boolean.TRUE);
            updateDTO.setUpdateUser(metadata.getUpdateUser());
            metadataRepository.update(updateDTO);

            // 搜索数据库中所有未完成的相同MD5元数据，更新为完成状态
            searchDto = new FileMetadataInfoDTO();
            searchDto.setFileMd5(metadata.getFileMd5());
            searchDto.setIsFinished(false);
            List<FileMetadataInfoVo> others = metadataRepository.list(searchDto);
            if(CollUtil.isNotEmpty(others)){
                for (FileMetadataInfoVo other : others) {
                    updateDTO = new FileMetadataInfoUpdateDTO();
                    updateDTO.setId(other.getId());
                    updateDTO.setIsFinished(Boolean.TRUE);
                    updateDTO.setUpdateUser(metadata.getUpdateUser());
                    metadataRepository.update(updateDTO);
                }
            }
        }else{
            if(!metadata.getUploadTaskId().equals(completeResultVo.getUploadTaskId())){
                FileMetadataInfoUpdateDTO updateDTO = new FileMetadataInfoUpdateDTO();
                updateDTO.setId(metadata.getId());
                updateDTO.setUploadTaskId(completeResultVo.getUploadTaskId());
                updateDTO.setUpdateUser(metadata.getUpdateUser());
                metadataRepository.update(updateDTO);
            }
        }
        return completeResultVo;
    }

    @Override
    public FileChunkVo checkchuck(Chunk chunk, Boolean isPrivate, String userId) {
        // 根据MD5查询文件是否已上传过

        FileChunkVo fileCheckResultVo=new FileChunkVo();
        FileMetadataInfoDTO searchDTO = new FileMetadataInfoDTO();
        searchDTO.setFileMd5(chunk.getIdentifier());
        List<FileMetadataInfoVo> list = metadataRepository.list(searchDTO);

        FileMetadataInfoSaveDTO saveDTO = new FileMetadataInfoSaveDTO();
        CreateUploadUrlReqBO bo = new CreateUploadUrlReqBO();
        if(CollUtil.isNotEmpty(list)){
            //  1.当前用户或其他用户上传过，且已完成，秒传，新增文件元数据
            for (FileMetadataInfoVo fileMetadataInfoVo : list) {
                if (fileMetadataInfoVo.getIsFinished()) {
                    // 秒传
                    saveDTO.setFileKey(IdUtil.fastSimpleUUID()); // 文件KEY
                    saveDTO.setFileMd5(chunk.getIdentifier()); // 文件md5
                    saveDTO.setFileName(chunk.getFilename()); // 文件名
                    saveDTO.setFileMimeType(fileMetadataInfoVo.getFileMimeType()); // MIME类型
                    saveDTO.setFileSuffix(fileMetadataInfoVo.getFileSuffix()); // 文件后缀
                    saveDTO.setFileSize(fileMetadataInfoVo.getFileSize()); // 文件长度
                    saveDTO.setStorageBucket(fileMetadataInfoVo.getStorageBucket()); // 存储桶
                    saveDTO.setStoragePath(fileMetadataInfoVo.getStoragePath()); // 存储桶路径
                    saveDTO.setIsFinished(fileMetadataInfoVo.getIsFinished()); // 状态 0:未完成 1:已完成
                    saveDTO.setIsPreview(fileMetadataInfoVo.getIsPreview()); // 预览图 0:无 1:有
                    saveDTO.setIsPrivate(isPrivate); // 是否私有 0:否 1:是
                    saveDTO.setCreateUser(userId); // 创建人
                    saveDTO.setUpdateUser(userId); // 修改人

                    FileMetadataInfoVo metadataInfoVo = metadataRepository.save(saveDTO);
                    fileCheckResultVo.setSkipUpload(true);
                    return fileCheckResultVo;
                }
                // 取得当前用户上传任务
                Optional<FileMetadataInfoVo> userUploaded = list.stream().filter(item -> userId.equals(item.getCreateUser())).findFirst();

                FileMetadataInfoVo uploadingMetadata;

                boolean isSelf = userUploaded.isPresent();
                if(!isSelf){
                    uploadingMetadata = list.stream()
                            .filter(FileMetadataInfoVo::getIsFinished)
                            .findAny()
                            .orElseGet(() -> list.stream()
                                    .filter(item -> !item.getIsFinished()).findFirst().get());
                }else{
                    uploadingMetadata = userUploaded.get();
                }
                // 上传过未完成-断点续传
                bo.setIsSequel(Boolean.TRUE);
                Set<Integer> uploaded=new HashSet<>();
                CompleteMultipartUploadRequest  request=new CompleteMultipartUploadRequest();
                request.setFileName(chunk.getFilename());
                request.setChunks(chunk.getTotalChunks());
                request.setUploadId(fileMetadataInfoVo.getUploadTaskId());
                try {
                    Part[] listParts=listParts(request);
                    if(listParts.length==chunk.getTotalChunks()){
                        chunk.setUpid(fileMetadataInfoVo.getUploadTaskId());
                        FileUploadResponse   fileUploadResponse = completeMultipartUpload(chunk);
                        fileCheckResultVo.setSkipUpload(fileUploadResponse.getIsComplete());

                    }
                    else {
                        if (listParts != null && listParts.length > 0) {
                            bo.setUploadIdActive(true);
                            for (Part b : listParts) {
                                uploaded.add(b.partNumber());
                            }
                            fileCheckResultVo.setUploaded(uploaded);
                        } else {
                            bo.setUploadIdActive(false);
                        }
                    }
                }
                catch (Exception e){
                    bo.setUploadIdActive(false);
                }

                if(!isSelf&&bo.getUploadIdActive()==true){
                    // 3.其他用户上传过，未完成，断点续传，新增文件元数据
                    // 插入自己的元数据
                    BeanUtils.copyProperties(uploadingMetadata, saveDTO);
                    saveDTO.setFileName(chunk.getFilename());
                    saveDTO.setCreateUser(userId);
                    saveDTO.setIsPrivate(isPrivate);
                    saveDTO.setUploadTaskId(fileMetadataInfoVo.getUploadTaskId());
                    FileMetadataInfoVo metadataInfoVo = metadataRepository.save(saveDTO);
                    fileCheckResultVo.setUploadId(fileMetadataInfoVo.getUploadTaskId());
                }else{
                    // 2.当前用户上传过，未完成，断点续传
                        FileMetadataInfoUpdateDTO updateDTO = new FileMetadataInfoUpdateDTO();
                        updateDTO.setId(uploadingMetadata.getId());
                        if(bo.getUploadIdActive()==true){

                            updateDTO.setUploadTaskId(uploadingMetadata.getUploadTaskId());

                        }else {
                            updateDTO.setUploadTaskId(getUploadId(chunk));
                        }
                    fileCheckResultVo.setUploadId(updateDTO.getUploadTaskId());
                        updateDTO.setUpdateUser(userId);
                        metadataRepository.update(updateDTO);
                }

            }
        }
        else {
            CreateUploadUrlRespBO createUploadUrlRespBO = this.createUploadUrl(bo,chunk);
            FileMetadataInfoVo metadataInfo = saveMetadataInfo(saveDTO, createUploadUrlRespBO,  chunk.getIdentifier(),  chunk.getFilename(),  chunk.getTotalSize(),  isPrivate, userId);
            fileCheckResultVo.setUploadId(getUploadId(chunk));
        }

        return  fileCheckResultVo;

    }

    public CreateUploadUrlRespBO createUploadUrl(CreateUploadUrlReqBO bo,Chunk chunk) {
        // 计算分块数量
        Integer chunkNum = chunk.getTotalChunks();
        // 分块信息集合
        List<FileCheckResultVo.Part> partList = new ArrayList<>();
        // 存储桶
        String bucketName;
        // 存储路径
        String storagePath;
        // 文件key
        String fileKey;
        // 上传任务编号
        String uploadId;
        // 断点续传
        if (Boolean.TRUE.equals(bo.getIsSequel()) && CollUtil.isNotEmpty(bo.getMissPartNum()) && CharSequenceUtil.isNotBlank(bo.getUploadId())) {
            // 断点续传需要使用已创建的任务信息构建分片信息
            // 存储桶
            bucketName = bo.getStorageBucket();
            // 存储路径
            storagePath = CommonUtil.getObjectName(bo.getFileMd5());
            // 文件key
            fileKey = bo.getFileKey();
            uploadId = bo.getUploadId();
            // 开始位置

        } else {
            // 获取文件后缀
            String suffix = FileUtil.getSuffix(bo.getFullFileName());
//            if (CharSequenceUtil.isBlank(suffix)) {
//                throw new MinioPlusException(MinioPlusErrorCode.FILE_SUFFIX_GET_FAILED);
//            }
            // 文件key
            fileKey = IdUtil.fastSimpleUUID();
            // 存储路径
            storagePath = CommonUtil.getPathByDate();

            // 存储桶
            bucketName = StorageBucketEnums.getBucketByFileSuffix(suffix);
            // 创建桶
            minioService.makeBucket(bucketName);
           // minioS3Client.makeBucket(bucketName);
            // 如果是图片并开启了压缩,不需要分片,返回项目上的接口地址
            if (bucketName.equals(StorageBucketEnums.IMAGE.getCode())) {

                FileCheckResultVo.Part part = new FileCheckResultVo.Part();
                // 图片上传时，直接使用fileKey作为uploadId
                part.setUploadId(fileKey);
                part.setUrl("/storage/upload/image/"+fileKey);
                part.setStartPosition(0L);
                part.setEndPosition(bo.getFileSize());
                partList.add(part);

                uploadId = fileKey;
            } else {
                // 创建分片请求,获取uploadId
                uploadId =getUploadId(chunk);


            }
        }
        CreateUploadUrlRespBO respBO = new CreateUploadUrlRespBO();
        // 桶名字
        respBO.setBucketName(bucketName);
        // 文件存储路径
        respBO.setStoragePath(storagePath);
        // 文件id-必填
        respBO.setFileKey(fileKey);
        // 分块数量-可选,分片后必须重新赋值 默认1
        respBO.setPartCount(chunkNum);
        // 切片上传任务id
        respBO.setUploadTaskId(uploadId);
        // 分片信息-必填
        respBO.setParts(partList);
        return respBO;
    }

    private String getUploadId(String bucketName, String objectName,Chunk chunk) {

        String extName = FileNameUtil.extName(chunk.getFilename());
        String fileName = IdUtil.fastSimpleUUID() + "." + extName;
        String path = DateUtil.format(new DateTime(), "yyyy/MM/dd");
       // String objectName =chunk.getFilename(); //path + "/" + fileName;
        Multimap<String, String> headers = HashMultimap.create();
        headers.put("Content-Type", chunk.getType());
        MultipartUploadCreate uploadCreate = MultipartUploadCreate.builder()
                .bucketName(bucketName)
                .objectName(objectName)
                .headers(headers)
                .build();
        String uploadId = minioService.uploadId(uploadCreate).result().uploadId();
        log.info("文件上传id：{}，文件名：{}", uploadId, objectName);
        return  uploadId;
    }


    /**
     * 保存文件源信息
     *
     * @param saveDTO                   元数据保存实体类
     * @param createUploadUrlRespBO     上传链接参数
     * @param fileMd5                   文件md5
     * @param fullFileName              文件名（含扩展名）
     * @param fileSize                  文件长度
     * @param isPrivate                 是否私有 false:否 true:是
     * @param userId                    用户编号
     * @return {@link FileMetadataInfoVo}
     */
    private FileMetadataInfoVo saveMetadataInfo(FileMetadataInfoSaveDTO saveDTO, CreateUploadUrlRespBO createUploadUrlRespBO,
                                                String fileMd5, String fullFileName, long fileSize, boolean isPrivate, String userId) {
        // 保存文件元数据
        String suffix = FileUtil.getSuffix(fullFileName);
        // 文件KEY
        saveDTO.setFileKey(createUploadUrlRespBO.getFileKey());
        // 文件md5
        saveDTO.setFileMd5(fileMd5);
        // 文件名
        saveDTO.setFileName(fullFileName);
        // MIME类型
        saveDTO.setFileMimeType(FileUtil.getMimeType(fullFileName));
        // 文件后缀
        saveDTO.setFileSuffix(suffix);
        // 文件长度
        saveDTO.setFileSize(fileSize);
        // 存储桶
        saveDTO.setStorageBucket(createUploadUrlRespBO.getBucketName());
        // 存储路径
        saveDTO.setStoragePath(createUploadUrlRespBO.getStoragePath());
        // 上传任务id
        saveDTO.setUploadTaskId(createUploadUrlRespBO.getUploadTaskId());
        // 状态 0:未完成 1:已完成
        saveDTO.setIsFinished(Boolean.FALSE);
        // 是否分片
        saveDTO.setIsPart(createUploadUrlRespBO.getPartCount() > 1);
        // 分片数量
        saveDTO.setPartNumber(createUploadUrlRespBO.getPartCount());
        // 预览图 0:无 1:有
        //saveDTO.setIsPreview(saveDTO.getStorageBucket().equals(StorageBucketEnums.IMAGE.getCode()) && properties.getThumbnail().isEnable());
        // 是否私有 0:否 1:是
        saveDTO.setIsPrivate(isPrivate);
        // 创建人
        saveDTO.setCreateUser(userId);
        // 修改人
        saveDTO.setUpdateUser(userId);
        return metadataRepository.save(saveDTO);
    }

    /**
     * 构建结果
     * 构建文件预检结果
     *
     * @param metadataInfo 元数据信息
     * @param partList     块信息
     * @param partCount    块数量
     * @param isDone       是否秒传
     * @return {@link FileCheckResultVo}
     */
    private FileCheckResultVo buildResult(FileMetadataInfoVo metadataInfo, List<FileCheckResultVo.Part> partList, Integer partCount, Boolean isDone) {
        FileCheckResultVo fileCheckResultVo = new FileCheckResultVo();
        // 主键
        fileCheckResultVo.setId(metadataInfo.getId());
        // 文件KEY
        fileCheckResultVo.setFileKey(metadataInfo.getFileKey());
        // 文件md5
        fileCheckResultVo.setFileMd5(metadataInfo.getFileMd5());
        // 文件名
        fileCheckResultVo.setFileName(metadataInfo.getFileName());
        // MIME类型
        fileCheckResultVo.setFileMimeType(metadataInfo.getFileMimeType());
        // 文件后缀
        fileCheckResultVo.setFileSuffix(metadataInfo.getFileSuffix());
        // 文件长度
        fileCheckResultVo.setFileSize(metadataInfo.getFileSize());
        // 是否秒传
        fileCheckResultVo.setIsDone(isDone);
        // 分块数量
        fileCheckResultVo.setPartCount(partCount);
        // 分块大小
       // fileCheckResultVo.setPartSize(properties.getPart().getSize());
        // 分块信息
        fileCheckResultVo.setPartList(partList);
        return fileCheckResultVo;
    }


    private String getFileUrl(String filePath) {
        return endpoint + "/" + bucketName + "/" + filePath;
    }
}
